﻿using Microsoft.InformationProtectionAndControl;
using Microsoft.Office.Crypto.Agile;
using OfficeAgile;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace CloudStorageLight.Core
{
    [ComImport]
    [Guid("0000000d-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IEnumSTATSTG
    {
        // The user needs to allocate an STATSTG array whose size is celt.
        [PreserveSig]
        uint
        Next(
        uint celt,
        [MarshalAs(UnmanagedType.LPArray), Out]
            System.Runtime.InteropServices.ComTypes.STATSTG[] rgelt,
            out uint pceltFetched
        );

        void Skip(uint celt);

        void Reset();

        [return: MarshalAs(UnmanagedType.Interface)]
        IEnumSTATSTG Clone();
    }

    public static class MsOffCryptoUtil
    {
        /// <summary>
        /// Copy the OLE Storage into temp files
        /// </summary>
        /// <param name="inputFile"></param>
        /// <param name="infoFile"></param>
        /// <param name="packageFile"></param>
        private static void FileToStreams(string inputFile, string infoFile, string packageFile)
        {
            var oleStorage = OleWrap.OpenReadStorage(inputFile);
            using (oleStorage)
            {
                oleStorage.Storage.CopyToFile("EncryptionInfo", infoFile);
                oleStorage.Storage.CopyToFile("EncryptedPackage", packageFile);
            }
        }

        /// <summary>
        /// Copy the temp files into a new OLE storage
        /// </summary>
        /// <param name="infoFile"></param>
        /// <param name="packageFile"></param>
        /// <param name="outputFile"></param>
        private static void StreamsToFile(string infoFile, string packageFile, string outputFile)
        {
            var oleStorage = OleWrap.CreateStorage(outputFile);
            using (oleStorage)
            {
                oleStorage.Storage.CopyFromFile("EncryptionInfo", infoFile);
                oleStorage.Storage.CopyFromFile("EncryptedPackage", packageFile);
            }
        }

        /// <summary>
        /// Load the encryption session from a given file
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        private static EncryptionSession LoadFromFile(string file)
        {
            var fileStream = new FileInfo(file).OpenRead();
            using (fileStream)
            {
                return EncryptionSession.LoadFromStream(fileStream);
            }
        }

        /// <summary>
        /// Decrypt the package into a file
        /// </summary>
        /// <param name="session"></param>
        /// <param name="encryptedPackageFile"></param>
        /// <param name="decryptedPackageFile"></param>
        private static void DecryptPackage(EncryptionSession session, string encryptedPackageFile, string decryptedPackageFile)
        {
            var encryptedPackageStream = File.OpenRead(encryptedPackageFile);
            using (encryptedPackageStream)
            {
                bool isValid = session.DoIntegrityCheck(encryptedPackageStream);
                Console.WriteLine("Integrity check: " + isValid);

                var decryptedPackageStreamRead = session.GetEncryptedStream(encryptedPackageStream);
                using (decryptedPackageStreamRead)
                {
                    decryptedPackageStreamRead.CopyToFile(decryptedPackageFile);
                }
            }
        }

        /// <summary>
        /// Encrypt the package from a file
        /// </summary>
        /// <param name="session"></param>
        /// <param name="decryptedPackageFile"></param>
        /// <param name="encryptedPackageFile"></param>
        private static void EncryptPackage(EncryptionSession session, string decryptedPackageFile, string encryptedPackageFile)
        {
            var encryptedPackageStream = File.Open(encryptedPackageFile, FileMode.Create, FileAccess.ReadWrite);
            using (encryptedPackageStream)
            {
                var encryptedPackageStreamWrapper = session.GetEncryptedStream(encryptedPackageStream);
                using (encryptedPackageStreamWrapper)
                {
                    encryptedPackageStreamWrapper.CopyFromFile(decryptedPackageFile);
                }

                session.AddIntegrityCheck(encryptedPackageStream);
            }
        }

        /// <summary>
        /// Write the encryption session out to disk
        /// </summary>
        /// <param name="session"></param>
        /// <param name="file"></param>
        private static void WriteToXml(EncryptionSession session, string file)
        {
            var encryptionInfoStream = File.Open(file, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
            using (encryptionInfoStream)
            {
                session.WriteToStream(encryptionInfoStream);
            }
        }

        /// <summary>
        /// パスワードを保持しているか？
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public static bool IsEncryptedPackage(string filePath)
        {
            OleStorage oleStorage = null;
            try
            {
                oleStorage = OleWrap.OpenReadStorage(filePath);
                using (oleStorage.Storage.OpenReadStream("EncryptedPackage"))
                {
                    return true;
                }
            }
            catch 
            {
                return false;
            }
            finally
            {
                if (oleStorage != null) oleStorage.Dispose();
            }

        }

        /// <summary>
        /// パスワードを解除する
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="password"></param>
        public static void Decrypto(string filePath, string password)
        {
            var originalEncryptedPackageFile = Path.GetTempFileName();
            var originalEncryptionInfoFile = Path.GetTempFileName();
            try
            {
                FileToStreams(filePath, originalEncryptionInfoFile, originalEncryptedPackageFile);
                var session = LoadFromFile(originalEncryptionInfoFile);
                session.UnlockWithPassword(password);

                DecryptPackage(session, originalEncryptedPackageFile, filePath);
            }
            finally
            {
                File.Delete(originalEncryptedPackageFile);
                File.Delete(originalEncryptionInfoFile);
            }
        }


        /// <summary>
        /// パスワードを付与する
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="password"></param>
        public static void SetPassword(string filePath, string password)
        {
            var session = OfficeAgileUtil.CreateSession(password);

            var newEncryptedPackageFile = Path.GetTempFileName();
            var newEncryptionInfoFile = Path.GetTempFileName();
            try
            {
                EncryptPackage(session, filePath, newEncryptedPackageFile);
                WriteToXml(session, newEncryptionInfoFile);

                StreamsToFile(newEncryptionInfoFile, newEncryptedPackageFile, filePath);
            }
            finally
            {
                File.Delete(newEncryptedPackageFile);
                File.Delete(newEncryptionInfoFile);
            }
        }

        /// <summary>
        /// Office用のパスワード可能なファイルか？
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static bool IsOfficeFilePasscode(string id)
        {
            var path = id.ToLower();
            if (path.EndsWith(".docx") || path.EndsWith(".xlsx") || path.EndsWith(".pptx"))
            {
                return true;
            }
            return false;
        }


        /// <summary>
        /// Office用のパスワード可能なファイルか？
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static bool IsRMSProtectedFilename(string id)
        {
            var path = id.ToLower();
            if (path.EndsWith(".pfile") || rmsSupportFile.Any(x => path.EndsWith(".p" + x)))
            {
                return true;
            }
            return false;
        }


        static string[] rmsOfficeNativeFile = "doc,dot,xla,xls,xlt,pps,ppt,docm,docx,dotm,dotx,xlam,xlsb,xlsm,xlsx,xltm,xltx,xps,potm,potx,ppsx,ppsm,pptm,pptx,thmx".Split(',');
        static string[] rmsSupportFile = "txt,xml,jpg,jpeg,pdf,png,tiff,bmp,gif,giff,jpe,jfif,jif".Split(',');

        public static string ConvertToRmsProtectedFileName(string filename)
        {
            var ext = Path.GetExtension(filename).ToLower();
            if (ext.Length > 0) ext = ext.Substring(1);

            if (rmsOfficeNativeFile.Contains(ext)) return filename;
            if (rmsSupportFile.Contains(ext)) return filename.Substring(0, filename.Length - ext.Length) + "p" + ext;
            return filename + ".pfile";
        }

        public static string ConvertFromRmsProtectedFileName(string filename)
        {
            var ext = Path.GetExtension(filename).ToLower();
            if (ext.Length > 0) ext = ext.Substring(1);

            if (rmsOfficeNativeFile.Contains(ext)) return filename;
            if (ext == "pfile") return filename.Substring(0, filename.Length - 6);
            if (ext.StartsWith("p")) return filename.Substring(0, filename.Length - ext.Length) + ext.Substring(1);
            return null;
        }

        public static async Task<string> EncryptRmsFile(BlobService svc, string id, string temp)
        {
            var sym = new SymmetricKeyCredential();
            sym.AppPrincipalId = svc.CloudAccount.RmsServicePrincipal;
            sym.Base64Key = svc.CloudAccount.RmsSymmetricKey;
            sym.BposTenantId = svc.CloudAccount.TenantId;
            var tempId = await svc.GetRmsTemplateIdAsync(id);
            var info = new ConnectionInfo(new Uri(svc.CloudAccount.RmsExtranetUrl), new Uri(svc.CloudAccount.RmsIntranetUrl));
            return RmsUtil.EncryptFile(info, sym, temp, tempId);
        }


    }
}
